package net.danielthompson.danray;

import net.danielthompson.danray.lights.Radiatable;
import net.danielthompson.danray.shading.Blender;
import net.danielthompson.danray.shading.Material;
import net.danielthompson.danray.shapes.*;
import net.danielthompson.danray.structures.Point;
import net.danielthompson.danray.states.IntersectionState;
import net.danielthompson.danray.structures.Scene;
import net.danielthompson.danray.structures.Vector;
import org.apache.commons.math3.util.FastMath;

import java.awt.*;

/**
 * User: daniel
 * Date: 7/2/13
 * Time: 15:26
 */
public class Tracer {

   Scene _scene;
   int _maxDepth;
   private final int _airIndexOfRefraction = 1;

   public Tracer(Scene scene, int maxDepth) {
      _scene = scene;
      _maxDepth = maxDepth;
   }

   public double GetAngleOfIncidence(Vector incomingRay, IntersectionState state) {
      Vector normal = state.Normal;

      Point normalDirection = normal.Direction;
      Point incomingDirection = incomingRay.Direction;

      double angleRadians = FastMath.acos(normalDirection.Dot(incomingDirection));

      double angleDegress = FastMath.toDegrees(angleRadians);

      return 180 - angleDegress;
   }

   public double GetAngleOfIncidencePercentage(Vector incomingRay, IntersectionState state) {
      double AoI = GetAngleOfIncidence(incomingRay, state);

      double AoIp = Math.abs(100 - 10 * AoI / 9);

      return AoIp;
   }

   public Color GetColorForRay(Vector ray, int depth) {
      return GetColorForRay(ray, depth, _airIndexOfRefraction);
   }

   public Color GetColorForRay(Vector ray, int depth, double oldIndexOfRefraction) {

      double brightness = 0;

      IntersectionState closestStateToRay = _scene.GetClosestDrawableToRay(ray);

      if (closestStateToRay == null) {
         if (depth == 1) {
            return Color.black;
         }
         else {
            return Color.magenta;
         }
      }

      Material objectMaterial = closestStateToRay.Drawable.GetMaterial();

      for (Radiatable radiatable : _scene.getRadiatables()) {
         Point intersectionPoint = closestStateToRay.IntersectionPoint;
         Point radiatableLocation = radiatable.getLocation();

         Vector lightRay = intersectionPoint.CreateVectorFrom(radiatableLocation);
         IntersectionState potentialOccluder = _scene.GetClosestDrawableToRay(lightRay);

         if (potentialOccluder == null || potentialOccluder.Drawable.equals(closestStateToRay.Drawable)) {

            double distanceFromLightSource = Math.sqrt(radiatable.getLocation().SquaredDistanceBetween(closestStateToRay.IntersectionPoint));

            for (Drawable drawable : _scene.getDrawables()) {
               //if (!drawable.equals(closestDrawableToRay)) {
               IntersectionState state = drawable.GetHitInfo(lightRay);
               if (state.Hits) {
                  if (drawable.equals(closestStateToRay.Drawable)) {
                     double angleOfIncidencePercentage = GetAngleOfIncidencePercentage(lightRay, closestStateToRay);
                     // case 1
                     if (angleOfIncidencePercentage >= 0 && angleOfIncidencePercentage <= 100) {
                        brightness +=  140 * radiatable.getLumens() * (angleOfIncidencePercentage) / (distanceFromLightSource * distanceFromLightSource);
                     }
                     // case 2
                     else {
                        // add no brightness
                     }
                  }
                  // case 3
                  else {
                     // add no brightness
                  }
               }
            }
         }
      }

      float[] hsbColor = Color.RGBtoHSB(objectMaterial.getColor().getRed(), objectMaterial.getColor().getGreen(), objectMaterial.getColor().getBlue(), null);

      hsbColor[2] = (float)brightness;

      if (hsbColor[2] >= 1.0f) {
         hsbColor[2] = 1.0f;
      }

      Color calculatedColor = Color.getHSBColor(hsbColor[0], hsbColor[1], hsbColor[2]);

      // base case
      if (depth >= _maxDepth) {
         return calculatedColor;
      }
      // recursive case
      else {
         depth++;
         // reflected color

         Color reflectedColor = null;

         if (objectMaterial.getReflectivity() > 0) {
            Vector reflectedRay = GetReflectedRay(closestStateToRay.IntersectionPoint, closestStateToRay.Normal, ray);
            reflectedColor = GetColorForRay(reflectedRay, depth, oldIndexOfRefraction);
         }

         // refracted color

         if (reflectedColor == Color.magenta) {
            return calculatedColor;
         }

         Color refractedColor = null;

         if (objectMaterial.getTransparency() > 0) {

            Vector refractedRay = GetRefractedRay(closestStateToRay, closestStateToRay.Normal, ray, oldIndexOfRefraction);
            refractedColor = GetColorForRay(refractedRay, depth, closestStateToRay.Drawable.GetMaterial().getIndexOfRefraction());
         }

         float reflectivity = (float)objectMaterial.getReflectivity();
         float transparency = (float)objectMaterial.getTransparency();
         try {
            Color[] colors = new Color[] {calculatedColor, reflectedColor, refractedColor };
            float[] weights = new float[] { (float)objectMaterial.getDiffuse(), reflectivity, transparency};
            Color blended = Blender.BlendRGB(colors, weights);
            return blended;
/*
            Color blended;
            if (refractedColor == null || refractedColor == Color.magenta) {
               blended = Blender.BlendRGB(calculatedColor, reflectedColor, reflectivity);
            }
            else {
               blended = Blender.BlendRGB(calculatedColor, reflectedColor, reflectivity, refractedColor, transparency);
            }
            return blended;*/
         }
         catch (IllegalArgumentException e) {
            System.out.println(e);
         }

      }
      return Color.magenta;
   }

   private Vector GetRefractedRay(IntersectionState state, Vector normal, Vector incomingRay, double oldIndexOfRefraction) {

      double n1 = oldIndexOfRefraction;
      double n2 = state.Drawable.GetMaterial().getIndexOfRefraction();
      double nRatio = n1 / n2;

      double cosTheta1 = -normal.Direction.Dot(incomingRay.Direction);
      double cosTheta2 = Math.sqrt(1 - nRatio * nRatio * (1 - (cosTheta1 * cosTheta1)));

      Point refractedDirection;

      if (cosTheta1 > 0) {
         refractedDirection = Point.Plus(incomingRay.Scale(nRatio), normal.Scale(nRatio * cosTheta1 - cosTheta2));
      }
      else {
         refractedDirection = Point.Plus(incomingRay.Scale(nRatio), normal.Scale(nRatio * cosTheta1 + cosTheta2));
      }

      Point offsetIntersection = Point.Plus(state.IntersectionPoint, Point.Scale(refractedDirection, .0000001));

      return new Vector(offsetIntersection, refractedDirection);
   }

   public Vector GetReflectedRay(Point intersectionPoint, Vector normal, Vector incomingRay) {
      double factor = incomingRay.Direction.Dot(normal.Direction) * 2;
      Point scaled = normal.Scale(factor);
      Point direction = Point.Minus(new Point(0, 0, 0), Point.Minus(scaled, incomingRay.Direction));

      Point offsetIntersection = Point.Plus(intersectionPoint, Point.Scale(direction, .0000001));

      //Point direction = normal.ScaleFromOrigin(incomingRay.Direction.Dot(normal.Direction) * 2).Minus(incomingRay.Direction);
      Vector reflectedVector = new Vector(offsetIntersection, direction);
      return reflectedVector;
   }


}
